﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PasteCodeTaskBase;
using PasteTimer.noticemodels;
using Volo.Abp.ObjectMapping;

namespace PasteTimer
{
    /// <summary>
    /// 压缩推送的自适应single或者slave模式
    /// </summary>
    public class NoticeNotifyHandler : IPasteCodeTaskBase
    {
        private IAppCache _cache;
        private IServiceProvider _serviceProvider;
        private ILogger<NoticeNotifyHandler> _logger;
        private readonly IObjectMapper _mapping;
        private readonly ChannelHelper _channelHelper;
        private TaskConfig _config;
        private SlaveHelper _slaveHelper;
        /// <summary>
        /// size.10
        /// </summary>
        private readonly SemaphoreSlim _semaphoresend;

        /// <summary>
        /// size.1
        /// </summary>
        private readonly SemaphoreSlim _semaphoretick;
        private IHttpClientFactory _client;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cache"></param>
        /// <param name="host"></param>
        /// <param name="logger"></param>
        /// <param name="client"></param>
        /// <param name="slaveHelper"></param>
        /// <param name="config"></param>
        /// <param name="channelHelper"></param>
        /// <param name="mapping"></param>
        public NoticeNotifyHandler(
            IAppCache cache,
            IHost host,
            ILogger<NoticeNotifyHandler> logger,
            IHttpClientFactory client,
            SlaveHelper slaveHelper,
            IOptions<TaskConfig> config,
            ChannelHelper channelHelper,
            IObjectMapper mapping)
        {
            _cache = cache;
            _serviceProvider = host.Services;
            _logger = logger;
            _channelHelper = channelHelper;
            _mapping = mapping;
            _client = client;
            _semaphoresend = new SemaphoreSlim(10);
            _semaphoretick = new SemaphoreSlim(1);
            diclog = new Dictionary<string, NoticeLogAddDto>();
            rates = new List<int>();
            _slaveHelper = slaveHelper;
            _config = config.Value;
            ReadNotice();
        }

        /// <summary>
        /// 任务的频率列表 比如300秒 600秒 表示5分钟有频率发送 10分钟有频率发送
        /// </summary>
        private List<int> rates;

        /// <summary>
        /// 需要推送的消息体
        /// </summary>
        private Dictionary<string, NoticeLogAddDto> diclog;

        public override bool IsLocationService()
        {
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        private async void ReadNotice()
        {
            try
            {
                var info = await _channelHelper.Notice.Reader.ReadAsync();
                if (info != null && info != default)
                {
                    if (!string.IsNullOrEmpty(info.Code))
                    {
                        if (_config.SingleModel || _slaveHelper.IsMaster)
                        {
                            var message = await ReadMessageInfoDto(info.Code);
                            if (message != null && message != default)
                            {
                                //skey="s{rate}_{code}_{objid}_{userid}"
                                var users = await ReadRevicerList();
                                if (users != null && users.Count > 0)
                                {
                                    var hitusers = users.Where(x => x.Codes == "|all|" || x.Codes.Contains($"|{info.Code}|")).ToList();
                                    if (hitusers != null && hitusers.Count > 0)
                                    {
                                        using var _scope = _serviceProvider.CreateScope();
                                        using var _dbContext = _scope.ServiceProvider.GetRequiredService<IPasteTimerDbContext>();
                                        foreach (var user in hitusers)
                                        {
                                            var skey = $"s{message.SendRate}_{info.Code}_{info.ObjId}_{user.Id}";
                                            if (!diclog.ContainsKey(skey))
                                            {
                                                var log = await SendNoticeToUrl(user.Url, user.Id, info);
                                                _dbContext.Add(log);
                                                info.Time = 0;
                                                AddToList(message.SendRate, skey, info);
                                            }
                                            else
                                            {
                                                diclog[skey].Time++;
                                            }
                                        }
                                        await _dbContext.SaveChangesAsync();
                                    }
                                }
                            }
                        }
                        else
                        {
                            //推送给远端
                            await _slaveHelper.PostNotify(info);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogException(ex);
                await Task.Delay(2000);
            }
            finally
            {
                ReadNotice();
            }
        }

        /// <summary>
        /// 推送消息 不等待
        /// </summary>
        /// <param name="url"></param>
        /// <param name="uid"></param>
        /// <param name="message"></param>
        private async void PostNoticeToUrl(string url, int uid, NoticeLogAddDto message)
        {
            try
            {
                await _semaphoresend.WaitAsync();

                var log = await SendNoticeToUrl(url, uid, message);
                using var _scope = _serviceProvider.CreateScope();
                using var _dbContext = _scope.ServiceProvider.GetRequiredService<IPasteTimerDbContext>();
                _dbContext.Add(log);
                await _dbContext.SaveChangesAsync();
            }
            catch (Exception exl)
            {
                _logger.LogException(exl);
            }
            finally
            {
                _semaphoresend.Release();
            }
        }


        /// <summary>
        /// 推送给用户 等待
        /// </summary>
        /// <param name="url"></param>
        /// <param name="uid"></param>
        /// <param name="message"></param>
        private async Task<NoticeLog> SendNoticeToUrl(string url, int uid, NoticeLogAddDto message)
        {
            var log = _mapping.Map<NoticeLogAddDto, NoticeLog>(message);

            try
            {
                log.CreateDate = DateTime.UtcNow;
                log.RevicerId = uid;

                var json = new { msgtype = "text", text = new { content = $"[通知] {message.Body} 发生{(message.Time == 0 ? 1 : message.Time)}次" } };
                var postdata = (Newtonsoft.Json.JsonConvert.SerializeObject(json));
                using (HttpContent httpcontent = new StringContent(postdata))
                {
                    httpcontent.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json;charset=utf-8");
                    var client = _client.CreateClient();
                    client.Timeout = TimeSpan.FromSeconds(10);
                    var response = await client.PostAsync(url, httpcontent);
                    if (response.IsSuccessStatusCode)
                    {
                        log.Success = true;
                    }
                    else
                    {
                        log.Success = false;
                    }
                }
            }
            catch (Exception exl)
            {
                _logger.LogException(exl);
                log.Success = false;
            }
            return log;
        }


        /// <summary>
        /// 获取接收者
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        private async Task<RevicerInfoDto> ReadRevicerItem(int id)
        {
            var item = await _cache.ReadObject<RevicerInfoDto>(string.Format(PublicString.CacheItemRevicer, id));
            if (item == null || item == default)
            {
                using var _scope = _serviceProvider.CreateScope();
                using var _dbContext = _scope.ServiceProvider.GetRequiredService<IPasteTimerDbContext>();

                var one = await _dbContext.RevicerInfo.Where(x => x.Id == id).AsNoTracking().FirstOrDefaultAsync();
                if (one != null && one != default)
                {
                    var onedto = _mapping.Map<RevicerInfo, RevicerInfoDto>(one);
                    _cache.SetObject<RevicerInfoDto>(string.Format(PublicString.CacheItemRevicer, id), onedto);
                    return onedto;
                }
            }
            return null;
        }

        /// <summary>
        /// 获取消息格式配置，里面有发送频率
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        private async Task<MessageInfoDto> ReadMessageInfoDto(string code)
        {
            var item = await _cache.ReadObject<MessageInfoDto>(string.Format(PublicString.CacheItemMessage, code));
            if (item == null || item == default)
            {
                using var _scope = _serviceProvider.CreateScope();
                using var _dbContext = _scope.ServiceProvider.GetRequiredService<IPasteTimerDbContext>();

                var one = await _dbContext.MessageInfo.Where(x => x.Code == code).AsNoTracking().FirstOrDefaultAsync();
                if (one != null && one != default)
                {
                    var onedto = _mapping.Map<MessageInfo, MessageInfoDto>(one);
                    _cache.SetObject<MessageInfoDto>(string.Format(PublicString.CacheItemMessage, code), onedto);
                    return onedto;
                }
            }
            return null;
        }


        /// <summary>
        /// 接收者列表
        /// </summary>
        /// <returns></returns>
        private async Task<List<RevicerInfoListDto>> ReadRevicerList()
        {
            var item = await _cache.ReadObject<List<RevicerInfoListDto>>(PublicString.CacheListRevicer);
            if (item == null || item == default)
            {
                using var _scope = _serviceProvider.CreateScope();
                using var _dbContext = _scope.ServiceProvider.GetRequiredService<IPasteTimerDbContext>();
                var one = await _dbContext.RevicerInfo.Where(x => x.IsEnable).AsNoTracking().ToListAsync();
                if (one != null && one != default)
                {
                    var onedto = _mapping.Map<List<RevicerInfo>, List<RevicerInfoListDto>>(one);
                    _cache.SetObject<List<RevicerInfoListDto>>(PublicString.CacheListRevicer, onedto);
                    return onedto;
                }
            }
            return null;
        }


        private void AddToList(int rate, string skey, NoticeLogAddDto info)
        {
            diclog.Add(skey, info);

            if (rate >= 2)
            {
                if (!rates.Contains(rate))
                {
                    rates.Add(rate);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="rate"></param>
        private void RemoveRateKey(int rate)
        {
            //diclog.Add(skey, info);
            if (diclog.Keys.Where(x => x.StartsWith($"s{rate}_")).Count() == 0)
            {
                rates.Remove(rate);
            }

        }


        /// <summary>
        /// 从skey中读取用户ID
        /// </summary>
        /// <param name="skey"></param>
        /// <returns></returns>
        private int ReadUserIdFromKey(string skey)
        {
            var strs = skey.Split('_');

            if (strs.Length >= 4)
            {
                int.TryParse(strs[3], out var uid);
                return uid;
            }

            return 0;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override string LocationTickRegex()
        {
            return "00:00:30";
        }


        /// <summary>
        /// 把未推送的错误信息推送出去
        /// </summary>
        public override void Dispose()
        {
            //这个事件触发的时候已经太迟了... .. .
            try
            {
                Task.Run(async Task<bool> () =>
                {
                    if (diclog != null && diclog.Count > 0)
                    {
                        var keys = diclog.Keys.ToList();
                        //这里没法入库，资源被释放了
                        //using var _scope = _serviceProvider.CreateScope();
                        //using var _dbContext = _scope.ServiceProvider.GetRequiredService<IPasteTimerDbContext>();
                        //var findcount = 0;
                        foreach (var one in keys)
                        {
                            var obj = diclog[one];
                            if (obj.Time > 0)
                            {
                                var uid = ReadUserIdFromKey(one);
                                if (uid > 0)
                                {
                                    var user = await ReadRevicerItem(uid);
                                    if (user != null && user != default)
                                    {
                                        if (!String.IsNullOrEmpty(user.Url))
                                        {
                                            var log = await SendNoticeToUrl(user.Url, uid, obj);
                                            //findcount++;
                                            //_dbContext.Add(log);
                                        }
                                    }
                                }
                            }
                        }
                        //推送日志入库
                        //if (findcount > 0)
                        //{
                        //    await _dbContext.SaveChangesAsync();
                        //}
                        diclog.Clear();
                        rates.Clear();
                    }

                    return true;

                }).Wait();
            }
            finally
            {

            }

            base.Dispose();

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        public override async Task<PasteTaskCallBackModel> Work(PasteTaskSharedModel info)
        {
            try
            {
                //是否需要判定是否集群模式，是否是管理员

                await _semaphoretick.WaitAsync();
                //有多少余数？
                if (rates.Count > 0)
                {
                    var now = DateTimeOffset.Now.ToUnixTimeSeconds();
                    List<int> dels = null;//待删除队列
                    foreach (var item in rates)
                    {
                        if (now % item == 0)
                        {
                            if (dels == null) { dels = new List<int>(); }

                            var keys = diclog.Keys.Where(x => x.StartsWith($"s{item}_")).ToList();
                            foreach (var one in keys)
                            {
                                var obj = diclog[one];
                                if (obj.Time > 0)
                                {
                                    //var skey = $"s{message.SendRate}_{info.Code}_{info.ObjId}_{user.Id}";
                                    var uid = ReadUserIdFromKey(one);
                                    if (uid > 0)
                                    {
                                        var user = await ReadRevicerItem(uid);
                                        if (user != null && user != default)
                                        {
                                            //await SendNoticeToUrl(user.Url, uid, obj);
                                            PostNoticeToUrl(user.Url, uid, obj);
                                        }
                                    }
                                    obj.Time = 0;
                                }
                                else
                                {
                                    diclog.Remove(one);
                                }
                            }
                            dels.Add(item);
                        }
                    }
                    if (dels != null)
                    {
                        foreach (var item in dels)
                        {
                            RemoveRateKey(item);
                        }
                    }
                }
            }
            finally
            {
                _semaphoretick.Release();
            }
            return new PasteTaskCallBackModel() { };
        }

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="date"></param>
        ///// <param name="time"></param>
        //public override async void TickWorkAsync(DateTimeOffset date, long time)
        //{
        //    try
        //    {
        //        //是否需要判定是否集群模式，是否是管理员

        //        await _semaphoretick.WaitAsync();
        //        //有多少余数？
        //        if (rates.Count > 0)
        //        {
        //            var now = DateTimeOffset.Now.ToUnixTimeSeconds();
        //            List<int> dels = null;//待删除队列
        //            foreach (var item in rates)
        //            {
        //                if (now % item == 0)
        //                {
        //                    if (dels == null) { dels = new List<int>(); }

        //                    var keys = diclog.Keys.Where(x => x.StartsWith($"s{item}_")).ToList();
        //                    foreach (var one in keys)
        //                    {
        //                        var obj = diclog[one];
        //                        if (obj.Time > 0)
        //                        {
        //                            //var skey = $"s{message.SendRate}_{info.Code}_{info.ObjId}_{user.Id}";
        //                            var uid = ReadUserIdFromKey(one);
        //                            if (uid > 0)
        //                            {
        //                                var user = await ReadRevicerItem(uid);
        //                                if (user != null && user != default)
        //                                {
        //                                    //await SendNoticeToUrl(user.Url, uid, obj);
        //                                    PostNoticeToUrl(user.Url, uid, obj);
        //                                }
        //                            }
        //                            obj.Time = 0;
        //                        }
        //                        else
        //                        {
        //                            diclog.Remove(one);
        //                        }
        //                    }
        //                    dels.Add(item);
        //                }
        //            }
        //            if (dels != null)
        //            {
        //                foreach (var item in dels)
        //                {
        //                    RemoveRateKey(item);
        //                }
        //            }
        //        }
        //    }
        //    finally
        //    {
        //        _semaphoretick.Release();
        //    }
        //}
    }
}
